#include <iostream>
#include <string>
#include <cstdio>

extern "C" {
#include "libavutil/frame.h"
#include "libavutil/mem.h"
#include "libavcodec/avcodec.h"
}

#define VIDEO_INBUF_SIZE    (20480)
#define VIDEO_REFILE_THRESH (4096)

static char errBuf[128] = {0};
static char* avGetError(int errNum) {
    av_strerror(errNum, errBuf, 128);
    return errBuf;
}

static void printVideoFormat(const AVFrame* frame) {
    std::cout << "width: " << frame->width << std::endl;
    std::cout << "height: " << frame->height << std::endl;
    std::cout << "format: " << frame->format << std::endl;
}

static void decode(AVCodecContext *decCtx, AVPacket *packet, AVFrame *frame, FILE *outFile) {
    int ret;
    //send the packet with the compressed data to the decoder
    ret = avcodec_send_packet(decCtx, packet);
    if (ret == AVERROR(EAGAIN))
        std::cerr << "Receive_frame and send_packet both returned EAGAIN, which is an API violation." << std::endl;
    else if (ret < 0) {
        std::cerr << "Error submitting the packet to the decoder, err: " << avGetError(ret) << ", pkt_size: " << packet->side_data << std::endl;
        return;
    }

    //read all the output frames(infile general there may be any number of them)
    while (ret >= 0) {
        //对于 frame, avcodec_receive_frame 内部每次都先调用
        ret = avcodec_receive_frame(decCtx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            std::cerr << "Error during decoding." << std::endl;
            exit(1);
        }

        static int s_printFormat = 0;
        if (s_printFormat == 0) {
            s_printFormat = 1;
            printVideoFormat(frame);
        }

        //一般 H264 默认为 AV_PIX_FMT_YUV420P
        //linesize[] 代表每行的字节数量，所以每行的偏移是 linesize[]
        for (int j = 0; j < frame->height; j++)
            fwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, outFile);

        for (int j = 0; j < frame->height/2; j++)
            fwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width/2, outFile);

        for (int j = 0; j < frame->height/2; j++)
            fwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width/2, outFile);
    }
}

/*
 * 注意不同分辨率的问题
 * 提取 H264:ffmpeg -i source.200kbps.768x320_10s.flv -vcodec libx264 -an -f h264 source.200kbps.768x320_10s.h264
 * 提取 MEPG2:ffmpeg -i source.200kbps.768x320_10s.flv -vcodec mpeg2video -an -f mpeg2video source.200kbps.768x320_10s.mpeg2
 * 播放:ffplay -pixel_format yuv420p -video_size 768x320 -framerate 25 source.200kbps.768x320_10s.yuv
 */
int main(int argc, char *argv[])
{
    std::string inFileName, outFileName;
    const AVCodec *codec;
    AVCodecContext *codecCtx = nullptr;
    AVCodecParserContext *parser = nullptr;
    int len = 0, ret = 0;
    FILE *inFile, *outFile;

    //AV_INPUT_BUFFER_PADDING_SIZE 在输入比特流结尾的要求附加分配字节的数量上进行解码
    uint8_t inbuf[VIDEO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t *data = nullptr;
    size_t dataSize = 0;
    AVPacket *packet = nullptr;
    AVFrame *decodedFrame = nullptr;

    if (argc <= 2) {
        std::cerr << "Usage: " << argv[0] << " <input file> <output file>" << std::endl;
        exit(0);
    }

    inFileName = std::string(argv[1]);
    outFileName = std::string(argv[2]);

    packet = av_packet_alloc();
    enum AVCodecID videoCodecId = AV_CODEC_ID_H264;

    if (inFileName.find("h264") != inFileName.npos)
        videoCodecId = AV_CODEC_ID_H264;
    else if (inFileName.find("mpeg2") != inFileName.npos)
        videoCodecId = AV_CODEC_ID_MPEG2VIDEO;
    else
        std::cout << "Default codec id: " << videoCodecId << std::endl;

    //查找解码器
    codec = avcodec_find_decoder(videoCodecId);
    if (codec == nullptr) {
        std::cerr << "Codec not found." << std::endl;
        exit(1);
    }

    //获取裸流解析器 AVcodecParserContext(数据) + AVCodecParser(方法)
    parser = av_parser_init(codec->id);
    if (parser == nullptr) {
        std::cerr << "Parser not found." << std::endl;
        exit(1);
    }

    //分配codec上下文
    codecCtx = avcodec_alloc_context3(codec);
    if (codecCtx == nullptr) {
        std::cerr << "Could not allocate video codec context." << std::endl;
        exit(1);
    }

    //将解码器和解码器上下文进行关联
    if (avcodec_open2(codecCtx, codec, NULL) < 0) {
        std::cerr << "Could not open codec.";
        exit(1);
    }

    //打开输入文件
    inFile = fopen(inFileName.c_str(), "rb");
    if (inFile == NULL) {
        std::cerr << "Could not open " << inFileName << std::endl;
        exit(1);
    }

    //打开输出文件
    outFile = fopen(outFileName.c_str(), "wb");
    if (outFile == NULL) {
        std::cerr << "Could not open " << outFileName << std::endl;
        exit(1);
    }

    //读取文件进行解码
    data = inbuf;
    dataSize = fread(inbuf, 1, VIDEO_INBUF_SIZE, inFile);

    while (dataSize > 0) {
        if (decodedFrame == nullptr) {
            decodedFrame = av_frame_alloc();
            if (decodedFrame == nullptr) {
                std::cerr << "Could not allocate video frame." << std::endl;
                exit(1);
            }
        }

        ret = av_parser_parse2(parser, codecCtx, &packet->data, &packet->size,
                               data, dataSize, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);

        if (ret < 0) {
            std::cerr << "Error while parsing." << std::endl;
            exit(1);
        }

        data     += ret; //跳过已解析的数据
        dataSize -= ret; //相应减少对应的缓存的大小

        if (packet->size > 0)
            decode(codecCtx, packet, decodedFrame, outFile);

        if (dataSize < VIDEO_REFILE_THRESH) {//数据少了则再次读取
            memmove(inbuf, data, dataSize); //将之前剩下的数据拷贝到 buffer 位置
            data = inbuf;
            //读取数据长度: VIDEO_INBUF_SIZE - dataSize
            len = fread(data + dataSize, 1, VIDEO_INBUF_SIZE - dataSize, inFile);
            if (len > 0)
                dataSize += len;
        }
    }

    //冲刷解码器
    packet->data = NULL; //让其进入 drain mode
    packet->size = 0;
    decode(codecCtx, packet, decodedFrame, outFile);

    fclose(outFile);
    fclose(inFile);

    avcodec_free_context(&codecCtx);
    av_parser_close(parser);
    av_frame_free(&decodedFrame);
    av_packet_free(&packet);

    std::cout << "Main finish, pleaer enter Enter and exit." << std::endl;

    return 0;
}
